/*
 Part of the GUI library for Processing 
 http://www.lagers.org.uk/g4p/index.html
 http://sourceforge.net/projects/g4p/files/?source=navbar

 Copyright (c) 2008 Peter Lager

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General
 Public License along with this library; if not, write to the
 Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 Boston, MA  02111-1307  USA
 */
package automenta.vivisect.gui;

import automenta.vivisect.gui.HotSpot.HSrect;
import automenta.vivisect.gui.StyledString.TextLayoutInfo;

import java.awt.Graphics2D;
import java.awt.font.TextLayout;
import java.util.LinkedList;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.event.MouseEvent;

/**
 * This class is the Button component.
 *
 * The button face can have either text or an image or both just pick the right
 * constructor.
 *
 * Three types of event can be generated :-  <br>
 * <b> PRESSED RELEASED CLICKED </b><br>
 *
 * To simplify event handling the button only fires off CLICKED events if the
 * mouse button is pressed and released over the button face (the default
 * behaviour). <br>
 *
 * Using
 * <pre>button1.fireAllEvents(true);</pre> enables the other 2 events for button
 * <b>button1</b>. A PRESSED event is created if the mouse button is pressed
 * down over the button face, the CLICKED event is then generated if the mouse
 * button is released over the button face. Releasing the button off the button
 * face creates a RELEASED event. <br>
 *
 * The image file can either be a single image which is used for all button
 * states, or be a composite of 3 images (tiled horizontally) which are used for
 * the different button states OFF, OVER and DOWN in which case the image width
 * should be divisible by 3. <br>
 * A number of setImages(...) methods exist to set button state images, these
 * can be used once the button is created.<br>
 *
 *
 * @author Peter Lager
 *
 */
public class GButton extends GTextIconAlignBase {

    // Mouse over status
    protected int status = 0;

    // Only report CLICKED events
    protected boolean reportAllButtonEvents = false;

    public GButton(PApplet theApplet, float p0, float p1, float p2, float p3) {
        this(theApplet, p0, p1, p2, p3, "");
    }

    public GButton(PApplet theApplet, float p0, float p1, float p2, float p3, String text) {
        super(theApplet, p0, p1, p2, p3);
        hotspots = new HotSpot[]{
            new HSrect(1, 0, 0, width, height) // control surface
        };
        setText(text);
        z = Z_SLIPPY;
        // Now register control with applet
        createEventHandler(GUI.applet, "handleButtonEvents",
                new Class<?>[]{GButton.class, GEvent.class},
                new String[]{"button", "event"}
        );
        registeredMethods = DRAW_METHOD | MOUSE_METHOD;
        cursorOver = HAND;
        GUI.addControl(this);
    }

    /**
     * If the parameter is true all 3 event types are generated, if false only
     * CLICKED events are generated (default behaviour).
     *
     * @param all
     */
    public void fireAllEvents(boolean all) {
        reportAllButtonEvents = all;
    }

    /**
     * Enable or disable the ability of the component to generate mouse
     * events.<br>
     * If the control is to be disabled when it is clicked then this will force
     * the mouse off button image is used.
     *
     * @param enable true to enable else false
     */
    public void setEnabled(boolean enable) {
        super.setEnabled(enable);
        if (!enable) {
            status = OFF_CONTROL;
        }
    }

    /**
     *
     * When a mouse button is clicked on a GButton it generates the
     * GEvent.CLICKED event. If you also want the button to generate
     * GEvent.PRESSED and GEvent.RELEASED events then you need the following
     * statement.<br>
     * <pre>btnName.fireAllEvents(true); </pre><br>
     *
     * <pre>
     * void handleButtonEvents(void handleButtonEvents(GButton button, GEvent event) {
     *	  if(button == btnName && event == GEvent.CLICKED){
     *        // code for button click event
     *    }
     * </pre> <br>
     * Where
     * <pre><b>btnName</b></pre> is the GButton identifier (variable name)
     * <br><br>
     *
     */
    public void mouseEvent(MouseEvent event) {
        if (!visible || !enabled || !available) {
            return;
        }

        if (event.getButton() == PConstants.LEFT) {

            calcTransformedOrigin(winApp.getCursorX(), winApp.getCursorY());
            currSpot = whichHotSpot(ox, oy);

            if (currSpot >= 0 || focusIsWith == this) {
                cursorIsOver = this;
            } else if (cursorIsOver == this) {
                cursorIsOver = null;
            }

            switch (event.getAction()) {
                case MouseEvent.PRESS:
                    if (focusIsWith != this && currSpot >= 0 && z > focusObjectZ()) {
                        dragging = false;
                        status = PRESS_CONTROL;
                        takeFocus();
                        if (reportAllButtonEvents) {
                            fireEvent(this, GEvent.PRESSED);
                        }
                        bufferInvalid = true;
                    }

                    break;
                case MouseEvent.CLICK:
                            // No need to test for isOver() since if the component has focus
                    // and the mouse has not moved since MOUSE_PRESSED otherwise we 
                    // would not get the Java MouseEvent.MOUSE_CLICKED event
                    if (focusIsWith == this) {
                        status = OFF_CONTROL;
                        bufferInvalid = true;
                        loseFocus(null);
                        dragging = false;
                        fireEvent(this, GEvent.CLICKED);
                    }
                    break;
                case MouseEvent.RELEASE:
                            // if the mouse has moved then release focus otherwise
                    // MOUSE_CLICKED will handle it
                    if (focusIsWith == this && dragging) {
                        if (reportAllButtonEvents) {
                            fireEvent(this, GEvent.RELEASED);
                        }
                        dragging = false;
                        loseFocus(null);
                        status = OFF_CONTROL;
                        bufferInvalid = true;
                    }
                    break;
                case MouseEvent.MOVE:
                    int currStatus = status;
                    // If dragged state will stay as PRESSED
                    if (currSpot >= 0) {
                        status = OVER_CONTROL;
                    } else {
                        status = OFF_CONTROL;
                    }
                    if (currStatus != status) {
                        bufferInvalid = true;
                    }
                    break;
                case MouseEvent.DRAG:
                    dragging = (focusIsWith == this);
                    break;
            }
        }
    }

    public void draw() {
        if (!visible) {
            return;
        }

        // Update buffer if invalid
        updateBuffer();
        winApp.pushStyle();

        winApp.pushMatrix();
        // Perform the rotation
        applyTransform();

        // Move matrix to line up with top-left corner
        winApp.translate(-halfWidth, -halfHeight);
        // Draw buffer
        winApp.imageMode(PApplet.CORNER);
        if (alphaLevel < 255) {
            winApp.tint(TINT_FOR_ALPHA, alphaLevel);
        }
        winApp.image(buffer, 0, 0);
        winApp.popMatrix();
        winApp.popStyle();
    }

    protected void updateBuffer() {
        if (bufferInvalid) {
            Graphics2D g2d = buffer.g2;
            // Get the latest lines of text
            LinkedList<TextLayoutInfo> lines = stext.getLines(g2d);
            bufferInvalid = false;
            buffer.beginDraw();
            // Back ground colour
            switch (status) {
                case OVER_CONTROL:
                    buffer.background(palette[6]);
                    break;
                case PRESS_CONTROL:
                    buffer.background(palette[14]);
                    break;
                default:
                    buffer.background(palette[4]);
            }
            g2d.setColor(jpalette[3]);
            g2d.setStroke(pen_1_0);
            g2d.drawRect(0, 0, (int) width - 1, (int) height - 1);
            // Calculate text and icon placement
            calcAlignment();
            // If there is an icon draw it
            if (iconW != 0) {
                buffer.image(bicon[status], siX, siY);
            }
            float wrapWidth = stext.getWrapWidth();
            float sx = 0, tw = 0;
            buffer.translate(stX, stY);
            for (TextLayoutInfo lineInfo : lines) {
                TextLayout layout = lineInfo.layout;
                buffer.translate(0, layout.getAscent());
                switch (textAlignH) {
                    case CENTER:
                        tw = layout.getVisibleAdvance();
                        tw = (tw > wrapWidth) ? tw - wrapWidth : tw;
                        sx = (wrapWidth - tw) / 2;
                        break;
                    case RIGHT:
                        tw = layout.getVisibleAdvance();
                        tw = (tw > wrapWidth) ? tw - wrapWidth : tw;
                        sx = wrapWidth - tw;
                        break;
                    case LEFT:
                    case JUSTIFY:
                    default:
                        sx = 0;
                }
                // display text
                g2d.setColor(jpalette[2]);
                lineInfo.layout.draw(g2d, sx, 0);
                buffer.translate(0, layout.getDescent() + layout.getLeading());
            }
            buffer.endDraw();
        }
    }
}
